/*
Author: Roarke Lynch
Version: 0.3.0
Documentation: http://code.google.com/p/jquery-datastore/
Description: providing key-value storage, binding, and publishing.

*/

//************************************************************************************************************
//        Core User Interface
//************************************************************************************************************
$.extend( {
	ds : function (queryString, parameters) {
		context = function(queryString, parameters) {
			var fn = eval("$.ds.fn");
			for(var i in fn) {
				this[i] = fn[i];
			};
			
			this.query = queryString;
			this.queryContext = $.ds.storage.root;
			this.params = parameters;
			this.allNodes = new Array();
			
			this.length = 0;
			
			this.each = function(action) {
				var an = this.allNodes;
				var cxt = this;
				$.ds.storage.transaction(function() {
					for (var i=0; i < an.length; i++) {
						var n = an[i];
						action.apply(null, [n, i, cxt]);
					};
				});
				return this;
			}; //Performs action on each node matching queryString
			this.one = function(action) {
				if(this.allNodes.length > 0) {
					var n = this.allNodes[0];
					var cxt = this;
					$.ds.storage.transaction(function() {
						action.apply(null, [n, 0, cxt]);
					});
				}
				return this;
			}; //Performs actions only the first node matching queryString
			this.reset = function() {
				this.allNodes = $.ds.query.execute(this.queryContext, this.query, this.params);
				this.node = null;
				this.length = this.allNodes.length;
				
				return this;
			}; //Rexectues the original query with parameters
		};
		
		var instance = null;
		if(arguments[0].constructor == $.ds.node) {
			var n = arguments[0];
			if(arguments.length == 1) {
				instance = new context( n.path(), {} );
				instance.allNodes = [n];
				instance.length = 1;
			}
			if(arguments.length > 1) {
				instance = new context( arguments[1], (arguments[2] === undefined ? {} : arguments[2]));
				instance.queryContext = arguments[0];
				instance.reset();
			}
		}
		else if(arguments[0].constructor == context) {
			instance = arguments[0];
		}
		else {
			instance = new context(queryString, (parameters === undefined ? {} : parameters)); 
			instance.reset();
		}
		return instance;
	}
});

//************************************************************************************************************
//        Core Developer Interface
//************************************************************************************************************
$.extend( $.ds, {
	/**************************************
	 **    Context Function Container    **
	 **************************************/
	fn : { },
	
	/*****************
	 **    Debug    **
	 *****************/
	debug : {
		assert : function(test, message) {
			if(test == false) {
				if(window.console) {
					window.console.assert(test, message);
				}
				throw (message);
			}
		},
		error : function(message) {
			$.ds.debug.assert(false, message);
		},
		info : function(message) {
			if(window.console) {
				window.console.info(message);
			}
		},
		warn : function(test, message) {
			if(window.console) {
				if(arguments.length > 1 && !test) {
					window.console.warn(message);
				}
				if(arguments.length == 1) {
					window.console.warn(test);
				}
			}
		}
	},
	/*****************************
	 **    Value Controllers    **
	 *****************************/
	kind : {
		base : function(node) {
			this.node = node;
			this.kind = function() { return null; }
			this.get = function() { };
			this.set = function() { };
			this.commit = function() { };
			this.revert = function() { };
			this.validChildName = function(name) { return false; };
		},
		value : function(node) {
			$.ds.kind.base.apply(this, arguments);
			this.unstable = null;
			this.stable = null;
			this.kind = function() {
				return 'value';
			}
			this.get = function() {
				return this.unstable;
			};
			this.set = function(value) {
				$.ds.debug.assert(this.node.canSetValue(value), "Regulator(s) will not allow this value to be set.");
				this.unstable = value;
				this.node.didSetValue(value);
			};
			this.commit = function() {
				//NOTE: This will cause problems if ARRAYs or OBJECTs are stored here.
				this.stable = this.unstable;
			};
			this.revert = function() {
				//NOTE: This will cause problems if ARRAYs or OBJECTs are stored here.
				this.unstable = this.stable;
			}
		},
		array : function(node) {
			$.ds.kind.base.apply(this, arguments);
			this.kind = function() {
				return 'array';
			}
			this.get = function() {
				var names = this.node.subnames();
				var arrayValue = [];
				for(var i = 0; i < names.length; i++) {
					var name = i;
					var value = this.node.get(name).__value.get();
					arrayValue.push(value);
				}
				return arrayValue;
			};
			this.set = function(arrayValue) {
				$.ds.debug.assert(arrayValue.constructor === Array, "Invalid array value");
				$.ds.debug.assert(this.node.canSetValue(arrayValue), "Regulator(s) will not allow this value to be set.");
				
				var subnames = this.node.subnames();
				for (var i=0; i < subnames.length; i++) {
					this.node.del( subnames[i] );
				};
				
				for(var index = 0; index < arrayValue.length; index++) {
					var value = arrayValue[index];
					if(this.node.has(index)) {
						this.node.get(index).__value.set(value);
					}
					else {
						this.node.add(index, value);
					}
				};
				this.node.didSetValue(arrayValue);
			};
			this.validChildName = function(name) {
				if(/^\d+$/.test(name)) {
					var names = this.node.subnames();
					return ( Number(name) <= names.length );
				}
				return false;
			};
		},
		object : function(node) {
			$.ds.kind.base.apply(this, arguments);
			this.kind = function() {
				return 'object';
			};
			this.get = function() {
				var names = this.node.subnames();
				var objValue = {};
				for(var i = 0; i < names.length; i++) {
					var name = names[i];
					var value = this.node.get(name).__value.get();
					objValue[name] = value;
				}
				return objValue;
			};
			this.set = function(objValue) {
				$.ds.debug.assert(objValue.constructor === Object, "Invalid object value");
				$.ds.debug.assert(this.node.canSetValue(objValue), "Regulator(s) will not allow this value to be set.");
				
				var subnames = this.node.subnames();
				for (var i=0; i < subnames.length; i++) {
					this.node.del( subnames[i] );
				};
				
				for(var name in objValue) {
					var value = objValue[name];
					if(this.node.has(name)) {
						this.node.get(name).__value.set(value);
					}
					else {
						this.node.add(name, value);
					}
				}
				this.node.didSetValue(objValue);
			};
			this.validChildName = function(name) {
				return /^[a-z][a-z0-9-_]*$/i.test(name);
			};
		},
		calculated : function(node) {
			$.ds.kind.base.apply(this, arguments);
			this.stable = {};
			this.unstable = {};
			this.kind = function() {
				return 'calculated';
			};
			this.get = function() {
				var args = [];
				for(var i = 0; i < this.unstable.keys.length; i++) {
					var key = this.unstable.keys[i];
					var cxt = this.node.gotoPath(key);
					
					args.push(cxt.__value.get());
				}
				return this.unstable.calculator.apply(null, args);
			};
			this.set = function(value, keys) {
				$.ds.debug.assert(this.node.canSetValue(value, keys), "Regulator(s) will not allow this value to be set.");
				$.ds.debug.assert(value.constructor == Function, "Calculator must be a function");
				$.ds.debug.assert(keys.constructor == Array, "Keys must be an array");
				this.unstable = { 
					calculator: value, 
					keys: keys 
				};
				this.node.didSetValue(value, keys);
			};
			this.commit = function() {
				this.stable = {
					calculator: this.unstable.calculator, 
					keys: this.unstable.keys
				}
			};
			this.revert = function() {
				this.unstable = {
					calculator: this.stable.calculator, 
					keys: this.stable.keys
				}
			}
		},
		reflector : function(node, propname) {
			$.ds.kind.base.apply(this, arguments);
			this.propname = propname;
			this.kind = function() {
				return 'reflector';
			}
			this.get = function() {
				return this.node[ this.propname ]();
			};
			this.set = function(value) {
				$.ds.debug.error("Reflector values cannot be set.")
			};
		}
	},
	/*********************
	 **    Node Name    **
	 *********************/
	nodename : function(value) {
		this.stable = value;
		this.unstable = value;
		this.get = function() { return this.unstable; }
		this.set = function(v) { this.unstable = v; }
		
		this.commit = function() { this.stable = this.unstable; }
		this.revert = function() { this.unstable = this.stable; }
	},
	/*********************
	 **    Node Set    **
	 *********************/
	nodeset : function() {
		this.added = {};
		this.deleted = {};
		this.stable = {};
		
		this.get = function(name) {
			return (this.stable[name] === undefined ? this.added[name] : this.stable[name]);
		};
		this.has = function(name) {
			return (this.get(name) !== undefined);
		};
		this.remove = function(name) {
			if(this.stable[name] !== undefined) {
				if(this.deleted[name] === undefined) {
					this.deleted[name] = this.stable[name];
				}
				delete this.stable[name];
			}
			if(this.added[name] !== undefined) {
				delete this.added[name];
			}
		};
		this.add = function(name, item) {
			this.remove(name);
			this.added[name] = item;
		};
		this.commit = function() {
			var newStable = {};
			for(var i in this.added) {
				newStable[i] = this.added[i];
			}
			for(var i in this.stable) {
				newStable[i] = this.stable[i];
			}
			this.stable = newStable;
			this.added = {};
			this.deleted = {};
		};
		this.revert = function() {
			var newStable = {};
			for(var i in this.deleted) {
				newStable[i] = this.deleted[i];
			}
			for(var i in this.stable) {
				newStable[i] = this.stable[i];
			}
			this.stable = newStable;
			this.added = {};
			this.deleted = {};
		};
		this.names = function() {
			var n = [];
			for(var i in this.added) {
				n.push(i);
			}
			for(var i in this.stable) {
				n.push(i);
			}
			return n;
		};
	},
	
	/****************
	 **    Node    **
	 ****************/
	node : function(name, parent) {
		//Private Properties
		this.__name = new $.ds.nodename(name);
		this.__parent = null;
		this.__children = new $.ds.nodeset();
		this.__metadata = new $.ds.nodeset();
		this.__value = null;
		
		//Regulation
		this.canDelete = function() {
			if(this.kind() == 'reflector') {
				return false;
			}
			
			for(var name in $.ds.regulation.table) {
				var regulator = $.ds.regulation.table[name];
				if(!regulator.canDelete(this)) {
					return false;
				}
			}
			return true; 
		}
		this.willDelete = function() { 
			for(var name in $.ds.regulation.table) {
				var regulator = $.ds.regulation.table[name];
				regulator.willDelete(this);
			}
		}
		this.didDelete = function() { 
			for(var name in $.ds.regulation.table) {
				var regulator = $.ds.regulation.table[name];
				regulator.didDelete(this);
			}
		}
		
		this.canMove = function(newPath) {
			if(this.kind() == 'reflector') {
				return false;
			}
			 
			for(var name in $.ds.regulation.table) {
				var regulator = $.ds.regulation.table[name];
				if(!regulator.canMove(this, newPath)) {
					return false;
				}
			}
			return true; 
		}
		this.willMove = function(newPath) { 
			for(var name in $.ds.regulation.table) {
				var regulator = $.ds.regulation.table[name];
				regulator.willMove(this, newPath);
			}
		}
		this.didMove = function(oldPath) { 
			for(var name in $.ds.regulation.table) {
				var regulator = $.ds.regulation.table[name];
				regulator.didMove(this, oldPath);
			}
		}
		
		this.canSetValue = function(value, keys) {
			if(this.kind() == 'reflector') {
				return false;
			}
			
			for(var name in $.ds.regulation.table) {
				var regulator = $.ds.regulation.table[name];
				if(!regulator.canSetValue(this, value, keys)) {
					return false;
				}
			}
			return true;
		}
		this.didSetValue = function(value, keys) { 
			for(var name in $.ds.regulation.table) {
				var regulator = $.ds.regulation.table[name];
				regulator.didSetValue(this, value, keys);
			}
		}
		
		this.canAddChild = function(name, value, keys) {
			if(this.kind() == 'reflector') {
				return false;
			}
			if(["@id", "@path", "@subpaths", "@name", "@subnames", "@kind"].indexOf(name) != -1) {
				return false;
			} 
			
			for(var name in $.ds.regulation.table) {
				var regulator = $.ds.regulation.table[name];
				if(!regulator.canAddChild(this, name, value, keys)) {
					return false;
				}
			}
			return true;
		}
		this.didAddChild = function(name, value, keys) { 
			for(var name in $.ds.regulation.table) {
				var regulator = $.ds.regulation.table[name];
				regulator.didAddChild(this, name, value, keys);
			}
		}
		
		this.canDeleteChild = function(name) {
			if(["@id", "@path", "@subpaths", "@name", "@subnames", "@kind"].indexOf(name) != -1) {
				return false;
			}
			
			for(var name in $.ds.regulation.table) {
				var regulator = $.ds.regulation.table[name];
				if(!regulator.canDeleteChild(this, name)) {
					return false;
				}
			}
			return true;
		}
		this.didDeleteChild = function(name) { 
			for(var name in $.ds.regulation.table) {
				var regulator = $.ds.regulation.table[name];
				regulator.didDeleteChild(this, name);
			}
		}
		
		this.canCommit = function() { 
			for(var name in $.ds.regulation.table) {
				var regulator = $.ds.regulation.table[name];
				if(!regulator.canCommit(this)) {
					return false;
				}
			}
			return true;
		}
		this.didCommit = function() { 
			for(var name in $.ds.regulation.table) {
				var regulator = $.ds.regulation.table[name];
				regulator.didCommit(this, name);
			}
		}
		
		//Utility
		this.__nodesetForName = function(n) {
			return (String(n).indexOf("@") == 0 ? this.__metadata : this.__children);
		};
		this.__addReflector = function(name) {
			var c_name = "@" + name;
			this.__metadata.add( c_name, new this.constructor(c_name) );
			var c = this.__metadata.get(c_name);
			c.__parent = this;
			c.__value = new $.ds.kind.reflector(this, name);
		};
		this.toString = function() {
			return "[Node " + this.path() + "]";
		}
		//Value Convience
		this.value = function() {
			if(arguments.length == 0) {
				return this.__value.get();
			}
			if(arguments.length == 1) {
				this.__value.set(arguments[0])
			}
			if(arguments.length > 1) {
				this.__value.set(arguments[0], arguments[1]);
			}
		};
		this.kind = function() {
			return this.__value.kind();
		};
		this.size = function() {
			return this.subnames().length;
		};
		this.isValidSubname = function(n) {
			if( /^[@][a-z][a-z0-9-_]*$/i.test(n) ) {
				return true;
			}
			return this.__value.validChildName(n);
		};
		
		//Paths
		this.id = function() {
			return 0;
		}
		
		this.path = function() {
			if(this.__parent == null) {
				return null;
			}
			var a = this.__parent.path();
			var b = this.__name.get();
			return ( a == null ? b : [a,b].join(".") );
		};
		this.subpaths = function() {
			var p = [];
			var n = this.__children.names();
			var a = this.path();
			for(var i = 0; i < n.length; i++) {
				var b = n[i];
				p.push( a == null ? b : [a,b].join(".") );
			}
			return p;
		};
		this.name = function() {
			return this.__name.get();
		};
		this.subnames = function() {
			return this.__children.names();
		};
		
		//Metadata
		this.metanames = function() {
			return this.__metadata.names();
		};
		this.metapaths = function() {
			var p = [];
			var n = this.__metadata.names();
			var a = this.path();
			for(var i = 0; i < n.length; i++) {
				var b = n[i];
				p.push( a == null ? b : [a,b].join(".") );
			}
			return p;
		};
		this.metadata = function() {
			var mdo = {};
			var mn = this.__metadata.names();
			for(var i = 0; i < mn.length; i++) {
				var name = mn[i];
				var value = this.__metadata.get(name).value();
				mdo[name] = value;
			};
			return mdo;
		};
		
		//Navigation
		this.root = function () {
			return (this.__parent == null ? this : this.__parent.root());
		};
		this.parent = function () {
			return this.__parent;
		};
		this.gotoPath = function(path) {
			var cxt = this.root();
			if(path.indexOf("../") == 0) {
				cxt = this;
				while(path.indexOf("../") == 0) {
					$.ds.debug.assert(cxt.parent() != null, "Treenode cannot be found.");
					cxt = cxt.parent();
					path = path.slice(3);
				}
			}
			else if(path.indexOf("./") == 0) {
				cxt = this;
				path = path.slice(2);
			}
			return cxt.get(path);
		};
		
		//Renaming & Moving
		this.rename = function(newName) {
			var p = this.path().split(".");
			p[ p.length - 1 ] = newName;
			var newPath = p.join(".");
			return this.moveTo(newPath);
		};
		
		this.moveTo = function(newPath) {
			var parts = newPath.split(".");
			var newName = parts.pop();
			var newParentPath = parts.join(".");
			var oldName = this.__name.get();
			var oldPath = this.path();
			
			$.ds.debug.assert( this.canMove(newPath), "Regulator(s) will not allow this node to move.");
			this.willMove(newPath);
			
			var siblings = this.__parent.__nodesetForName(oldName);
			siblings.remove( oldName );
			
			this.__name.set(newName);
			
			var siblings = this.root().get(newParentPath).__nodesetForName(newName);
			siblings.add( newName, this );
			
			this.didMove(oldPath);
			return this;
		};
		
		//Mutation & Query
		this.has = function(path) {
			if(path == null && this.__path == null) {
				return true;
			}
			if(path.constructor == String) {
				if(path == "") {
					return true;
				}
				path = path.split(".");
			}
			if(path.constructor == Number) {
				path = [path];
			}
			if(path.length == 0) {
				return true;
			}
			var next = path.shift();
			var ns = this.__nodesetForName(next);
			if( ns.has(next) == false) {
				return false;
			}
			
			return ns.get(next).has(path);
		};
		this.get = function(path) {
			if(path == null && this.__path == null) {
				return this;
			}
			if(path.constructor == String) {
				if(path == "") {
					return this;
				}
				path = path.split(".");
			}
			if(path.constructor == Number) {
				path = [path];
			}
			if(path.length == 0) {
				return this;
			}
			var next = path.shift();
			var ns = this.__nodesetForName(next);
			$.ds.debug.assert(ns.has(next), "Treenode cannot be found.");
			return ns.get(next).get(path);
		};
		this.add = function(path, value, keys) {
			if(path.constructor == String) {
				path = path.split(".");
			}
			if(path.constructor == Number) {
				path = [path];
			}
			var next = path.shift();
			
			$.ds.debug.assert(this.isValidSubname(next), "Treenode will not accept a child named '" + next + "'.");
			var ns = this.__nodesetForName(next);
			
			if(path.length == 0) {
				$.ds.debug.assert(ns.has(next) == false, "Treenode already exists");
				$.ds.debug.assert(this.canAddChild(next, value, keys), "Treenode regulator(s) will not accept this child");
				
				var newChild = new this.constructor(next);
				newChild.__parent = this;
				ns.add(next, newChild);
				newChild.initWithValueAndKeys(value, keys);
				this.didAddChild(next, value, keys);
				
				return ns.get(next);
			}
			
			if(ns.has(next) == false) {
				$.ds.debug.assert(this.canAddChild(next, {}), "Treenode regulator(s) will not accept this child");
				
				var newChild = new this.constructor(next);
				newChild.__parent = this;
				ns.add(next, newChild);
				newChild.initWithValueAndKeys({});
				this.didAddChild(next, {});
			}
			
			return ns.get(next).add(path, value, keys);
		};
		this.del = function(path) {
			if(path.constructor == String) {
				path = path.split(".");
			}
			if(path.constructor == Number) {
				path = [path];
			}
			var next = path.shift();
			var ns = this.__nodesetForName(next);
			
			$.ds.debug.assert(ns.has(next), "Treenode cannot be found.");
			
			if(path.length == 0) {
				var child = ns.get(next);
				$.ds.debug.assert(this.canDeleteChild(next), "Treenode regulator(s) will allow this child node to be deleted.");
				$.ds.debug.assert(child.canDelete(), "Treenode regulator(s) will allow this node to be deleted.");
				$.ds.debug.assert(child.kind() != 'reflector', "Reflector nodes cannot be deleted.");
				child.willDelete();
				ns.remove(next);
				child.didDelete();
				this.didDeleteChild(next);
				return;
			}
			return ns.get(next).del(path);
		};
		
		//Transactions
		this.commit = function () {
			$.ds.debug.assert(this.canCommit(), "Treenode regulator(s) will allow this node to be commited.");
			this.__name.commit();
			this.__value.commit();
			this.__children.commit();
			this.__metadata.commit();
			this.__finalize(this.__children, false);
			this.__finalize(this.__metadata, false);
			return true;
		};
		this.revert = function () {
			this.__name.revert();
			this.__value.revert();
			this.__children.revert();
			this.__metadata.revert();
			this.__finalize(this.__children, true);
			this.__finalize(this.__metadata, true);
			return true;
		};
		this.__finalize = function(ns, shouldRevert) {
			var n = ns.names();
			for(var i = 0; i < n.length; i++) {
				var name = n[i];
				var child = ns.get(name);
				if(shouldRevert) {
					child.revert();
				}
				else {
					child.commit();
				}
			}
			
			var n = ns.names();
			for(var i = 0; i < n.length; i++) {
				var name = n[i];
				var child = ns.get(name);
				$.ds.debug.assert(name == child.name(), "Finalize failed, name mismatch");
				$.ds.debug.assert(this === child.parent(), "Finalize failed, parent mismatch");
			}
		};

		//Value Controller
		this.initWithValueAndKeys = function(value, keys) {
			if( this.__parent != null & 
				["@id", "@path", "@subpaths", "@name", "@subnames", "@kind"].indexOf( this.name() ) == -1) {
				this.__addReflector("id");
				this.__addReflector("kind");
				this.__addReflector("path");
				this.__addReflector("subpaths");
				this.__addReflector("name");
				this.__addReflector("subnames");
				this.__metadata.commit();
				this.__finalize(this.__metadata, false);
			}
			
			if(value !== undefined && value !== null) {
				switch(value.constructor) {
					case Array :
						this.__value = new $.ds.kind.array(this);
						this.__value.set(value);
						break;
					case Object :
						this.__value = new $.ds.kind.object(this);
						this.__value.set(value);
						break;
					case Function :
						$.ds.debug.assert(keys !== undefined, "Keys Not Supplied");
						this.__value = new $.ds.kind.calculated(this);
						this.__value.set(value, keys);
						break;
					default :
						this.__value = new $.ds.kind.value(this);
						this.__value.set(value);
				}
			}
			else if (value === undefined) {
				this.__value = new $.ds.kind.object(this);
			}
			else if (value == null) {
				this.__value = new $.ds.kind.value(this);
				this.__value.set(null);
			}
			else {
				$.ds.debug.error("Fatal Unreachable Case");
			}
		};
		
		if(arguments.length == 0) {
			this.__value = new $.ds.kind.object(this); //Make a ROOT node an OBJECT
		}
		else {
			this.__value = new $.ds.kind.base(this); //Make all other node BASE until INITed
		}
	},
	/********************************
	 **    Regulation Controller   **
	 ********************************/
	regulation : {
		table : { },
		addRegulator : function(name, obj) {
			var regulator = new $.ds.regulation.regulator_interface();
			for(var i in obj) {
				regulator[i] = obj[i];
			}
			$.ds.regulation.table[name] = regulator;
		},
		removeRegulator : function(name) {
			delete $.ds.regulation.table[name];
		},
		regulator_interface : function() {
			this.canDelete = function(node) { return true; }
			this.willDelete = function(node) { }
			this.didDelete = function(node) { }
			
			this.canMove = function(node, newPath) { return true; }
			this.willMove = function(node, newPath) { };
			this.didMove = function(node, oldPath) { };
			
			this.canSetValue = function(node, value, keys) { return true; }
			this.didSetValue = function(node, value, keys) { }
			
			this.canAddChild = function(node, name, value, keys) { return true; }
			this.didAddChild = function(node, name, value, keys) { }
			
			this.canDeleteChild = function(node, name) { return true; }
			this.didDeleteChild = function(node, name) { }
			
			this.canCommit = function(node) { return true; }
			this.didCommit = function(node) { return true; }
		}
	},
	/******************
	 **    Modules   **
	 ******************/
	modules : { 
		
	},
	loadModule : function(name) { 
		$.ds.debug.assert($.ds.modules[name], "Module '" + name + "' cannot be found");
		try {
			$.ds.modules[name].load();
			$.ds.debug.info("Loaded module '" + name + "'");
		}
		catch(e) {
			$.ds.debug.error("Could not load module '" + name + "'. " + e);
		}
	},
	unloadModule : function(name) {
		$.ds.debug.assert($.ds.modules[name], "Module '" + name + "' cannot be found");
		try {
			$.ds.modules[name].unload();
			$.ds.debug.info("Unloaded module '" + name + "'");
		}
		catch(e) {
			$.ds.debug.error("Could not unload module '" + name + "'. " + e);
		}
	}
});


//************************************************************************************************************
//        Default Modules
//        - Storage (Transactions, Dependencies, Caching)
//        - Query (Execution)
//        - Common (Value, Remove, Insert)
//        - Validation (PARTIALLY IMPLEMENTED)
//        - Classes (NOT IMPLEMENTED)
//************************************************************************************************************
$.extend($.ds.modules, {
	/*************************
	 **    Storage Module   **
	 *************************/
	storage : {
		load : function() {
			$.ds.storage = new $.ds.modules.storage.storage_interface();
			$.ds.regulation.addRegulator("Cache", new $.ds.modules.storage.cache_regulator() );
			$.ds.regulation.addRegulator("Storage Metadata", new $.ds.modules.storage.metadata_regulator() );
		},
		unload : function() {
			delete $.ds.storage;
			$.ds.regulation.removeRegulator("Cache");
			$.ds.regulation.removeRegulator("Storage Metadata");
		},
		cache_regulator : function() {
			this.willDelete = function(node) { $.ds.storage.markNode(node.path()) };
			this.didSetValue = function(node) { $.ds.storage.markNode(node.path()) };
			this.didAddChild = function(node) {
				var p = node.path(); 
				$.ds.storage.markNode(p); 
			};
			this.didDeleteChild = function(node) {
				var p = node.path(); 
				$.ds.storage.markNode(p); 
			};
			this.willMove = function(node, newPath) { $.ds.storage.markNode(newPath) };
			this.didMove = function(node, oldPath) {
				$.ds.storage.markNode(oldPath);
				var p = node.path(); 
				$.ds.storage.markNode(p);
			};
		},  //Notifies the changeset when a value or child is changed
		metadata_regulator : function () {
			this.didSetValue = function(node, value, keys) {
				if(node.kind() == 'calculated') {
					//NOTE: Should this store function source and keys to metadata?
					var path = node.path();
					var resolvedKeys = [];
					for(var i = 0; i < keys.length; i++) {
						var parentPath = node.gotoPath(keys[i]).path();
						$.ds.storage.addDependent(parentPath, path)
						resolvedKeys.push(parentPath);
					}
					$.ds.storage.setKeypaths(path, resolvedKeys); //What paths this path requires
				}
			};
			this.willDelete = function(node) {
				if(node.kind() == 'calculated') {
					var path = node.path();
					keypaths = $.ds.storage.getKeypaths(path);
					for (var i=0; i < keypaths.length; i++) {
						var parent = keypaths[i];
						$.ds.storage.removeDependent(parent, path);
					};
				}
				
			};
		},
		storage_interface : function () {
			this.root = new $.ds.node();
			this.root.initWithValueAndKeys({});
			
			this.cache = {};
			this.changeset = null;
			this.inTransaction = false;
			
			//
			//Value
			//
			this.getValue = function(path) {
				if(this.cache[path] !== undefined) {
					return this.cache[path];
				}
				var v = this.root.get(path).value();
				this.cache[path] = v;
				return v;
			};
			this.setValue = function(path, value, keys) { 
				$.ds.debug.assert(this.inTransaction, "No Transaction");
				
				if(this.root.has(path) == false) {
					this.root.add(path, value, (keys === undefined ? undefined : keys));
				}
				else {
					this.root.get(path).value(value, (keys === undefined ? undefined : keys));
				}
			};
			this.hasValue = function(path) { 
				return this.root.has(path);
			};
			this.deleteValue = function(path) { 
				$.ds.debug.assert(this.inTransaction, "No Transaction");
				this.root.del(path);
			};
			
			//
			//Transaction
			//
			this.transaction = function(action) {
				this.inTransaction = true;
				this.changeset = {}; 
				try {
					action.apply(null);
				}
				catch(e) {
					this.root.revert();
					$(document).trigger("revertdatastore", [this.changeset]);
					this.inTransaction = false;
					$.ds.debug.error("Transaction Failed (" + e + ")");
				}
				this.root.commit();
				$(document).trigger("commitdatastore", [this.changeset]);
				
				this.inTransaction = false;
			};
			this.markNode = function(path, deep) { 
				$.ds.debug.assert(this.changeset != null, "No Transaction");
				if(this.cache[path] !== undefined) {
					delete this.cache[path];
				}
				this.changeset[path] = true;
				var dependents = this.getDependents(path);
				for(var i = 0; i < dependents.length; i++) {
					this.markNode(dependents[i]);
				}
				
				if(deep) {
					var c = this.root.get(path);
					var names = c.names().concat(c.metanames());
					for(var i = 0; i < names.length; i++) {
						this.markNode( [path, names[i]].join("."), true);
					}
				}
			};

			//
			// Dependencies
			//
			this.getDependents = function(parentPath) {
				var mp = [parentPath, "@dependents"].join(".");
				if( $.ds.storage.hasValue(mp) ) {
					return $.ds.storage.getValue(mp);
				}
				return [];
			};
			this.addDependent = function(parentPath, childPath) {
				var mp = [parentPath, "@dependents"].join(".");
				var dependents = $.ds.storage.getDependents(parentPath);
				dependents.push(childPath);
				$.ds.storage.setValue(mp, dependents);
			};
			this.removeDependent = function(parentPath, childPath) {
				var mp = [parentPath, "@dependents"].join(".");
				var dependents = $.ds.storage.getDependents(parentPath);
				var newDependents = [];
				for (var i=0; i < dependents.length; i++) {
					if(dependents[i] != childPath) {
						newDependents.push(childPath);
					}
				};
				$.ds.storage.setValue(mp, newDependents);
			};
			this.setKeypaths = function(path, keypaths) {
				var mp = [path, "@keypaths"].join(".");
				$.ds.storage.setValue(mp, keypaths);
			};
			this.getKeypaths = function(path) {
				var mp = [path, "@keypaths"].join(".");
				if( $.ds.storage.hasValue(mp) ) {
					return $.ds.storage.getValue(mp);
				}
				return [];
			};
		}
	},
	
	/*************************
	 **    Query Module   **
	 *************************/
	query : {
		load : function() { 
			$.ds.query = new $.ds.modules.query.query_interface();
		},
		unload : function() { 
			delete $.ds.query;
		},
		
		query_interface : function () {
			this.execute = function(node, statement, parameters) {
				$.ds.debug.assert(node !== undefined, "Cannot execute query, no node provided.");
				$.ds.debug.assert(node != null, "Cannot execute query, null node provided.");
				$.ds.debug.assert(statement.constructor == String, "Cannot execute query, statement is not a string.");
				
				var clausePattern = /^(((?:[@]?[a-z0-9_-]+)|(?:\*)|(?:@\*))(?:\[(.*?)])?)(\.(?!$)|$)/i;
				var clause = clausePattern.exec(statement);
				
				$.ds.debug.assert(clause != null, "Invalid query statement '" + statement + "'");
				
				var next_statement = statement.substring(clause[0].length);
				var is_at_end = (next_statement.length == 0);
				
				var names = (clause[2] == "*" ? node.subnames() : (clause[2] == "@*" ? node.metanames() : [clause[2]]));
				var condition = clause[3];
				var condition_parameters = (parameters === undefined || parameters == null ? {} : parameters);
				var matching_nodes = [];
				
				for (var i=0; i < names.length; i++) {
					if(node.has(names[i])) {
						var test_node = node.get(names[i]);
						if( this.test(test_node, condition, condition_parameters) ) {
							if(is_at_end) {
								matching_nodes.push(test_node);
							}
							else {
								matching_nodes = matching_nodes.concat( this.execute(test_node, next_statement, condition_parameters) );
							}
						}
					}
				};
				
				
				return matching_nodes;
			};
			
			this.test = function(node, conditionStatement, parameters) {
				if(conditionStatement == undefined) {
					return true;
				}
				
				var tests = conditionStatement.split(/\s*,\s*/);
				var conditionalPattern = /^([@]?[a-z0-9_-]+)\s+(\S+)\s+([$][a-z][a-z0-9_-]*|\d+|["][^"]*["]|['][^']*['])$/i; //"
				for (var i=0; i < tests.length; i++) {
					var test = tests[i];
					var conditional = conditionalPattern.exec(test);
					$.ds.debug.assert(conditional != null, "Invalid condition '" + conditionStatement + "', '" + test + "' is a mal-formed test.");
					
					var test_path = conditional[1];
					var operator_name = conditional[2];
					var value_name = conditional[3];
					
					$.ds.debug.assert(this.operators[operator_name] !== undefined, "Cannot evaluate condition '" + conditionStatement + "', operator '" + operator_name + "' not found.");
					
					if( node.has(test_path) == false ) {
						return false;
					}
					
					var actual_value = node.get(test_path).value();
					var test_value = null;
					if(value_name[0] == '$') {
						var p_name = value_name.substring(1);
						$.ds.debug.assert(parameters[p_name] !== undefined, "Cannot evaluate condition '" + conditionStatement + "', parameter '" + value_name + "' not found.");
						test_value = parameters[p_name];
					}
					else {
						eval("test_value = " + value_name);
					}
					
					var operator = this.operators[operator_name];
					
					if(operator.apply(null, [actual_value, test_value]) == false) {
						return false;
					}
				};
				
				return true;
			};
			
			this.operators = {
				"<" : function(actual, test) { return actual < test; },
				"<=" : function(actual, test) { return actual <= test; },
				">" : function(actual, test) { return actual > test; },
				">=" : function(actual, test) { return actual >= test; },
				"=" : function(actual, test) { return actual == test; },
				"!=" : function(actual, test) { return actual != test; }
			};
		},
	},
	/************************
	 **    Common Module   **
	 ************************/
	common : {
		load : function() {
			for(var i in $.ds.modules.common.user) {
				$.ds.fn[i] = $.ds.modules.common.user[i];
			}
		},
		unload : function() {
			for(var i in $.ds.modules.common.user) {
				delete $.ds.fn[i];
			}
		},
		user : {
			value : function(v, k) {
				//Get Value
				if(arguments.length == 0) {
					var value = undefined;
					if(this.length <= 1) {
						this.one(function(node){
							value = node.value();
						});
					}
					else {
						value = [];
						this.each(function(node) {
							value.push(node.value());
						});
					}
					return value;
				}
				//Set Value
				else {
					var value = arguments[0];
					var keys = arguments[1];
					var path = this.query;
					
					//Create
					if(this.length == 0) {
						$.ds.storage.transaction(function() {
							$.ds.storage.setValue(path, value, keys);
						});
						this.reset();
					}
					//Overwrite
					else {
						this.each(function(node) {
							node.value(value, keys);
						});
					}
					return this;
				}
			},
			remove : function (opt_at) {
				//Delete Value
				if(arguments.length == 0 ) {
					this.each(function(node) {
						$.ds.storage.deleteValue( node.path() );
					});
				}
				//Delete Subvalue
				else {
					var subpath = arguments[0];
					this.each(function(node) {
						//Delete All Subvalues
						if(subpath == "*") {
							var sn = node.subnames();
							for(var i = 0; i < sn.length; i++) {
								node.del(sn[i]);
							}
						}
						//Delete Specific Subvalue
						else {
							if(node.kind() == "array") {
								var l = node.size();
								node.del(subpath);
								for(var i = subpath; i < (l - 1); i++) {
									node.get( i + 1 ).rename(i);
								}
							}
							else {
								node.del(subpath);
							}
						}
					});
				}
				
				this.reset();
				return this;
			},
			insert : function(opt_at, v, k) {
				//Insert at end of array
				if(arguments.length == 1) {
					var value = arguments[0];
					this.each(function(node) {
						$.ds.debug.assert(node.kind() == 'array', "Node is not an array.");
						var l = node.size();
						node.add(l, value);
					});
				}
				//Insert at specific place
				else if(arguments.length > 1) {
					var at = arguments[0];
					var value = arguments[1];
					var keys = arguments[2];
					this.each(function(node) {
						//Metadata
						if(String(at).indexOf("@") == 0) {
							var p = [node.path(), at].join(".");
							if(node.has(at)) {
								node.get(at).value(value, keys);
							}
							else {
								node.add(at, value, keys);
							}
						}
						//Node Value
						else {
							switch(node.kind()) {
								case 'array' :
									var l = node.size();
									$.ds.debug.assert(at <= l, "Index outside array bounds.");
									for(var i = (l - 1); i >= at; i--) {
										node.get(i).rename(i + 1);
									}
									node.add(at, value, keys);
									break;
								case 'object' :
									if(node.has(at)) {
										node.get(at).value(value, keys);
									}
									else {
										node.add(at, value, keys);
									}
									break;
							}
						}
					});
				}
				return this;
			}
		}
	},
	
	/****************************
	 **    Validators Module   **
	 ****************************/
	validators : {
		load : function() { 
			$.ds.validators = new $.ds.modules.validators.validator_interface();
			$.ds.regulation.addRegulator("Validate", new $.ds.modules.validators.validation_regulator() );
			for(var i in $.ds.modules.validators.user) {
				$.ds.fn[i] = $.ds.modules.validators.user[i];
			}
		},
		unload : function() {
			$.ds.regulation.removeRegulator("Validate");
			for(var i in $.ds.modules.validators.user) {
				delete $.ds.fn[i];
			}
			delete $.ds.validators;
		},
		
		validation_regulator : function () {
			this.didSetValue = $.ds.modules.validators.validate_node;
		},
		
		validate_node : function(node) {
			$tn = $.ds(node, "@validation.tests");
			if($tn.length == 0) {
				return;
			}
			
			var nodeValue = node.value();
			var testNames = $tn.value();
			
			var passed = [];
			var failed = [];
			var messages = [];
			
			for(var i = 0; i < testNames.length; i++) {
				var testName = testNames[i];
				var test = $.ds.validators.get(testName);
				var testResult = undefined;
				
				try {
					testResult = test.apply(null, [nodeValue]);
				}
				catch(e) {
					testResult = e;
				}
				
				if(testResult != undefined) {
					messages.push(testResult);
					failed.push(testName);
				}
				else {
					passed.push(testName);
				}
			}
			
			$.ds(node).insert("@validation.passed", passed);
			$.ds(node).insert("@validation.failed", failed);
			$.ds(node).insert("@validation.messages", messages);
			$.ds(node).insert("@validation.ok", (failed.length == 0) );
		},
		
		user : {
			valid : function(validator_0, validator_1, validator_n) {
				if(arguments.length == 0) {
					var isValid = true;
					this.one(function(node) {
						var $ok = $.ds(node, "@validation.ok");
						if( $ok.length == 1 ) {
							isValid = $ok.value();
						}
					});
					return isValid;
				}
				else {
					var names = [];
					for(var i = 0; i < arguments.length; i++) {
						var name = arguments[i];
						$.ds.debug.assert( $.ds.validators.has(name), "Validator named '" + name + "' is not found.");
						names.push(name);
					}
					this.each(function(node) {
						$.ds(node).insert("@validation.tests", names);
						$.ds.modules.validators.validate_node(node);
					});
					return this;
				}
			}
		},
		
		validator_interface : function() {
			this.has = function(name) {
				var path = ["@validators", name].join(".");
				return $.ds.storage.hasValue(path);
			},
			this.add = function(name, validator) {
				$.ds.debug.assert(validator.constructor === Function, "Invalid Validator Function.");
				var path = ["@validators", name].join(".");
				var value = validator.toString();
				$.ds.storage.transaction(function() {
					$.ds.storage.setValue(path, value);
				});
			},
			this.get = function(name) {
				var path = ["@validators", name].join(".");
				var source = null;
				$.ds.storage.transaction(function() {
					source = $.ds.storage.getValue(path);
				});
				var f = null;
				eval("f = " + source);
				return f;
			}
		}
	}
});